#!/usr/bin/env python
# -*-coding:utf-8 -*-

__all__ = ['Map', 'show', 'getNodesNum', 'getEdgesNum', 'shortestPath',
           'getAllNodes', 'getAllEdges', 'getAllNodesCoords', 'getAllEdgesWeights', 'getNode', 'getEdge',
           'getNodeCoord', 'getEdgeWeight', 'getNodeNeibs', 'getPredecessors', 'getSuccessors', 'getXYRegion',
           '__hash__']

import networkx as nx
import numpy as np
from vector import Vector
import re
import hashlib
        

class Map(object):
    """
    地图。机器人在该地图上移动。
    """
    def __init__(self, file_name):
        """
        创建一个地图对象。
        :param file_name: 地图文件名。从该文件读入节点坐标、连边关系等，由此创建地图对象。
        """
        self._g = nx.DiGraph()
        self._nodes = []
        self._nodes_coords = {}
        self._edges = []
        self._edge_weights = {}
        self._hash = hashlib.md5()

        try:
            f = open(file_name)
        except IOError:
            print('file open error')
        else:
            edges_with_weights = []
            while True:
                line = f.readline()
                if not line:
                    break

                line = line.strip()
                self._hash.update(line.encode('utf-8'))
                m = re.findall('[-+]?\d*\.\d+|\d+', line)
                if len(m) == 3:
                    nid = m[0].strip()
                    x = float(m[1])
                    y = float(m[2])
                    self._nodes.append(nid)
                    self._nodes_coords[nid] = Vector(float(x), float(y))
                elif len(m) == 2:
                    s = m[0]
                    t = m[1]
                    m = re.findall('bidirectional|directional', line)
                    if m is not None:
                        weight = (self._nodes_coords[s]-self._nodes_coords[t]).norm()
                        edges_with_weights.append((s, t, weight))
                        self._edge_weights[(s, t)] = weight
                        self._edges.append((s, t))
                        if m[0].strip() == 'bidirectional':
                            edges_with_weights.append((t, s, weight))
                            self._edge_weights[(t, s)] = weight
                            self._edges.append((t, s))

            self._g.add_nodes_from(self._nodes)
            self._g.add_weighted_edges_from(edges_with_weights)
        finally:
            f.close()

    def getGraph(self):
        """
        获取图（图论意义的图）。
        :return: 图。通过Networkx中的DiGraph表示。
        """
        return self._g
        
    def show(self):
        """
        显示地图。通过Networkx显示。
        """
        import matplotlib.pyplot as plt
        pos = {}
        labels = {}
        for i in range(self.getNodesNum()):
            n = self._nodes[i]
            #nx.draw_networkx_edges(self._g, self._nodes_coords[i])
            #nx.draw_networkx_labels(self._g, self._nodes_coords[i])
            pos[n] = [self._nodes_coords[n].getX(), self._nodes_coords[n].getY()]
            labels[n] = i+1
        g = self._g.to_undirected()
        nx.draw_networkx_nodes(g, pos=pos, node_color="g", node_size=170, alpha=0.4)
        nx.draw_networkx_edges(g, pos=pos)
        nx.draw_networkx_labels(g, pos, labels, font_size=10)
        plt.show()
        
    def isEdgeBidirectional(self, edge):
        return (edge in self._edges) and ((edge[1], edge[0]) in self._edges)

    def getNodesNum(self):
        """
        获取地图中节点的数量。
        :return: 节点数量。
        """
        return len(self._nodes)

    def getEdgesNum(self):
        """
        获取地图中连边的数量。
        :return: 连边数量。
        """
        return len(self._edges)

    #def shortestPath(self, source, target, avoid_edges=None, weight='weight'):
    #    """
    #    获取从节点source到target的最短路径。
    #    :return: 从节点source到target的最短路径，用路径上所有节点的列表表示。
    #    """
    #    return dijkstra_path(self._g, source, target, avoid_edges=avoid_edges, weight='weight')

    def getAllNodes(self):
        """
        获取所有节点。
        :return: 所有节点的一个列表。
        """
        return self._nodes

    def getAllEdges(self):
        """
        获取所有连边。
        :return: 所有连边的一个列表。
        """
        return self._edges

    def getAllNodesCoords(self):
        """
        获取所有节点的坐标。
        :return: 所有节点坐标的一个字典。键-节点，值-节点坐标（Vector对象）。
        """
        return self._nodes_coords

    def getAllEdgesWeights(self):
        return self._edge_weights

    def getNode(self, idx):
        """
        获取索引为idx的节点。
        :return: 索引为idx的节点。
        """
        return self._nodes[idx]

    def getEdge(self, idx):
        """
        获取索引为idx的连边。
        :return: 索引为idx的连边。
        """
        return self._edges[idx]

    def getNodeCoord(self, idx):
        """
        获取索引/节点名为idx的节点的坐标。
        :param idx: 当idx为整型时，表示欲获取坐标的节点的索引。当idx为字符串类型时，表示欲获取坐标的节点名。
        :return: 节点坐标(Vector对象）。
        """
        if isinstance(idx, int):
            return self._nodes_coords[self._nodes[idx]]
        elif isinstance(idx, str):
            return self._nodes_coords[idx]

    def getEdgeWeight(self, idx):
        """
        获取索引为idx的连边的权重。
        :return: 索引为idx的连边的权重。
        """
        if isinstance(idx, int):
            return self._edge_weights[self._edge[idx]]
        elif isinstance(idx, tuple):
            return self._edge_weights[idx]

    def getNodeNeibs(self, id):
        """
        获取ID为id的节点的所有邻居节点。注：一个节点的id与索引无需相等。
        :return: 邻居节点列表。
        """
        return self._g.neighbors(id)

    def getPredecessors(self, id):
        """
        获取ID为id的节点的父节点。
        :return: 父节点列表。
        """
        return self._g.predecessors(id)

    def getSuccessors(self, id):
        """
        获取ID为id的节点的子节点。
        :return: 子节点列表。
        """
        return self._g.successors(id)

    def getXYRegion(self):
        """
        获取所有节点分布区域。
        :return: (min_x, min_y, max_x, max_y)，其中(min_x, min_y)为分布区域左上角的坐标、(max_x, max_y)为右下角的坐标。
        """
        min_x = float('inf')
        min_y = float('inf')
        max_x = -float('inf')
        max_y = -float('inf')
        for n, c in self._nodes_coords.items():
            x = c.getX()
            y = c.getY()
            if x < min_x:
                min_x = x
            if x > max_x:
                max_x = x
            if y < min_y:
                min_y = y
            if y > max_y:
                max_y = y
        return (min_x, min_y, max_x, max_y)
        
    def __hash__(self):
        """
        获取本地图的一个哈希表示。
        """
        #print('Hash is ', int(self._hash.hexdigest(), 16))
        return int(self._hash.hexdigest(), 16)
        
    def getLeafNodes(self):
        """
        获取所有的叶子节点。
        
        """
        return [x for x in self._g.nodes() if self._g.out_degree(x)==1 and self._g.in_degree(x)==1 and \
                   set(self.getPredecessors(x)) ==  set(self.getSuccessors(x))]


if __name__ == "__main__":
    import matplotlib.pyplot as plt

    map = Map('map1.txt')

    print(map.getNodesNum())

    #print(map.getNodeNeib('ID3'))
    map.show()




